{
 "cells": [
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## 1.导包\n",
    "torchvision 是 PyTorch 的一个扩展库，它为一些常见的图像处理任务提供了数据集、模型架构和常用的转换方法。torchvision 包含了以下几个主要的模块：\n",
    "\n",
    "- datasets: 这个模块包含了多个常用的数据集，如 CIFAR-10, MNIST, ImageNet 等等，这些数据集可以直接加载，并且已经进行了预处理，方便用户直接使用。\n",
    "- models: 提供了一系列流行的深度学习模型及其预训练权重，例如 ResNet, VGG, Inception 等。这使得研究人员和开发者可以快速地使用这些模型进行研究或开发工作，或者基于这些模型进行微调（fine-tuning）以适应特定的任务。\n",
    "- transforms: 提供了丰富的图像变换功能，用于数据增强（data augmentation），比如随机裁剪、翻转、颜色抖动等。这些变换对于提高模型的泛化能力非常有用。\n",
    "- utils: 包含了一些实用工具函数，比如将图像张量保存为文件的功能。\n",
    "- io: 提供了对多媒体文件的支持，包括读取视频文件的能力。"
   ],
   "id": "fb137373a95beffc"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-07T01:06:54.498709Z",
     "start_time": "2024-11-07T01:06:50.640881Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import torch\n",
    "from matplotlib.font_manager import FontProperties\n",
    "from torchvision.datasets import MNIST\n",
    "from torch.utils.data import DataLoader\n",
    "from torchvision.transforms import transforms\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.font_manager as fm"
   ],
   "id": "7536250a4573f7f",
   "outputs": [],
   "execution_count": 2
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 2.导入MNIST数据",
   "id": "c7ce74757ae739f9"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-07T01:06:54.618961Z",
     "start_time": "2024-11-07T01:06:54.500776Z"
    }
   },
   "cell_type": "code",
   "source": [
    "mnist_train = MNIST('../data', train=True, transform=transforms.ToTensor(), download=True)\n",
    "mnist_test = MNIST('../data', train=False, transform=transforms.ToTensor(), download=True)\n",
    "len(mnist_train), len(mnist_test),type(mnist_train)"
   ],
   "id": "7688bc3b289017a3",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(60000, 10000, torchvision.datasets.mnist.MNIST)"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 3
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 3.小批量获取数据集",
   "id": "5ee575b287c40dc2"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-07T01:06:54.799923Z",
     "start_time": "2024-11-07T01:06:54.620457Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# dataset: 数据集 shuffle: 是否随机；batch_size: 批量获取数据的大小；num_workers: 使用的进程数\n",
    "train_loader = DataLoader(dataset=mnist_train,shuffle=True,batch_size=10,num_workers=4)\n",
    "test_loader = DataLoader(dataset=mnist_test,shuffle=False,batch_size=10,num_workers=4)\n",
    "images,labels = next(iter(train_loader)) # 获取一组数据"
   ],
   "id": "7ad4d8eff220c375",
   "outputs": [],
   "execution_count": 4
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 4.绘制图片",
   "id": "4cb461891df1d912"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-07T01:46:19.274526Z",
     "start_time": "2024-11-07T01:46:18.753888Z"
    }
   },
   "cell_type": "code",
   "source": [
    "print(images[0].shape) # 通道为1，28*28像素\n",
    "# 设置字体\n",
    "import os\n",
    "\n",
    "# 获取项目根目录\n",
    "project_root = os.path.dirname(os.path.abspath(\"../fonts\"))\n",
    "font_path = os.path.join(project_root, 'fonts', 'simsun.ttc')\n",
    "font_properties = fm.FontProperties(fname=font_path, style='normal')\n",
    "plt.rcParams['font.family'] = font_properties.get_name()\n",
    "\n",
    "def get_labels(labels):\n",
    "    text_labels = [\"t-shirt(T恤)\", \"trouser(裤子)\", \"pullover(套衫)\", \"dress(连衣裙)\", \"coat(外套)\", \"sandal(凉鞋)\",\"shirt(衬衫)\", \"sneaker(运动鞋)\",\n",
    "                   \"bag(包)\", \"ankle boot(短靴)\"]\n",
    "    return [text_labels[int(label)] for label in labels ]\n",
    "\n",
    "# subplots：创建包含多个子图的图形容器；例如：以下代码创建一个2行5列的子图布局，每个图像宽度为4英寸，高度为4英寸\n",
    "# fig：整个图形的容器；axes：包含所有子图的数组\n",
    "fig,axes = plt.subplots(2,5,figsize=(9,4))\n",
    "for rows in range(2):\n",
    "    for cols in range(5):\n",
    "        index = rows * cols\n",
    "        ax = axes[rows][cols]\n",
    "        ax.imshow(images[index].numpy().reshape(28,28))\n",
    "        # int(Tensor): 将其转换为整数\n",
    "        ax.set_title(get_labels([labels[index]]))\n",
    "        ax.axis('off')"
   ],
   "id": "fd8dfbf0426c89ca",
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([1, 28, 28])\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n",
      "findfont: Font family 'SimSun' not found.\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 900x400 with 10 Axes>"
      ],
      "image/png": "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Vq9erebNm+u5555TjRo19Oeffyo+Pl6ff/65Zs6cqaioKD355JMKCQlRixYtVL58eSUkJGjChAkqUaKEmjRpIkm67bbb1KFDB9WrV0/h4eHau3ev5s2bp2bNmrluUJ7dBwxP5ecDy/XSLpB49913VaxYMQUHBysmJsbyb+7rDRo0SHPnzlX79u01ZswYValSRStXrtT06dP1zDPPuNaIRUZG6p577tGECRMUHh6uKlWqaO3atVq8eLEx3s6dO9W/f3917dpVN998swoXLqyvv/5aO3fu1NChQyVdK8DHjBmj4cOH69ChQ2rXrp3Cw8N18uRJbdmyxfWJ3k5sbKxmzZql/fv3e3Wx4o3atGmT7r777ly3Xi23e//99zVv3jw9+uijunLlilq2bKkpU6aoVKlSkqSgoCB99tlnGjhwoPr27atChQrpnnvu0Zo1a1wX76Rp166dPv30U40cOVKPPPKIIiMj1a9fPx0/flzz5s0z+j7++OOaNWuWtm7d6jqOeWvZsmUKCgrS3/72txv74QsIu9dYkv7+97/r+PHjmjlzpmbPnq26detqxowZHv2tuzNs2DBFRERo6tSpWrhwoUJCQhQbG6vx48e7Loq+Xu/evfXUU08pJCTE7brYbt26qXLlypo4caL69u2rP/74QxEREbr11ls9vr1gixYtFB0drWXLlmX5e+CFCxe0du1ajR07Nkv3k1Oogfwnp2qgbC2a69atqzVr1uiFF15Qz549FR4eru7du6tLly566qmnPBqjfPny2rZtm8aOHatJkybp6NGjKlasmGJiYlyFiiTdeeediouL06JFi3Tu3DmVKVNGd9xxh+bOnes6C9CqVSstX75ckydP1qVLl1SxYkX16NHDuDI6Ow8YnsrvB5brxcTE6K233tKUKVMUGxurlJQUzZ49O9MDftmyZbVhwwYNGzZMw4YN0++//66qVatq4sSJev75542+8+bN04ABA/TSSy8pJSVFHTt21Pz5842buEdGRqpatWqaPn26jhw5IofDoapVq+qNN94wztYNGzZMtWvX1pQpUzR//nxduXJFkZGRatKkiXH7scx06tRJYWFhWrZsmWvpR1Y5ePCgfvzxR48e81pQJCcny+FwuC6mSq9Xr16u3Lv33nszHatWrVpatWqVpf36+8+m6dy5szp37uyKk5KSdOutt1oeJtCqVSvdcsstmjVr1g0XzbNnz1bXrl1t/7vgdDqVkpJiueg1r7N7jdOEh4e7nsqZEYfDoSFDhlj+VtPfnis6Otryul+fS9fr06eP8S/2zDz55JN68sknM+1z11132d7CctSoURkeBwICAtS/f3/9z//8j15//XWFhIR4NLcbsXDhQjkcDstdhdxJSUlx+7eUm1ED+UdO1kB+LZo9ORg1b97c7fPi0//hZ/YM9jJlymjKlCmaMmVKhn169Ohhez/TCRMmaMKECZn2yc4Dhqfy04HFk5wZOHCgBg4caGnP7H7ClStXtqxJdCcyMlIff/yxpf3631lERITrKnU7nTp1UqdOnTLtExcXl+H9JIsXL65evXpp9uzZGjx4cJaeAY6Li1PlypVd9wvOTHJyslJSUrJsLrnB4cOHXRfU7Nq1K1v33adPH917772uM0IzZ87U3r173R7jJk6cqAcffFDDhw9XVFSUV/v59ttvtXXrVttHp0vXzkg/+OCDXo2f2+Xka5yXPfvss/r3v/+tadOmafDgwVmyj+TkZL322msaNmyYR2cPS5cunW3rrD1FDZQ9crIG8lvRnJ8PRtlxwPBUfjiwpMnPOeOLESNGaO7cufr000/18MMPZ8k+EhMTNW3aNL399tsqVCjzw0BiYmK+XwY0atQo9e/fX5Jy5E3hjz/+0ODBg3Xq1CkFBQWpYcOG+vzzz3XPPfdY+rZr106TJk3SL7/8oqioKBUqVEjLly/P8CmBKSkprg9fZ86c0dy5c1W1alXbOcXGxhr3cs7rFw7m9GuclwUHB2vevHnG003DwsIyvAgrNTVVkydP9qrfkSNH1K1bN73wwgsezWndunWu+09n5206M5Kf38+ogf7icPqhBI+Pj3fdcSAkJMR165P85L///a927Njh+nd8dHR0ho9TTTsQ9OnTx+/9fvnlF82bN08vvviiR7eB+f77740DS/r1lDmlIOSML1asWKFz586pe/fuunjxoiIjIzM8e5GamqrFixerWbNmHvW75557tGPHDq1Zs8ajs9kpKSnGm2WlSpW4cBAA/k9BeD+jBrrGL0UzAAAAkJ9l632aAQAAgLyIohkAAACwQdEMAAAA2PD47hn3BnTNynkgF1idar31mr+QP/lfVuaPRA4VBByD4AvyB77wJH840wwAAADYoGgGAAAAbFA0AwAAADYomgEAAAAbFM0A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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 17
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 5.初始化模型参数",
   "id": "2c9096aff4f5aaff"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "",
   "id": "c83cd87ba4bc3b5"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "num_inputs = 28*28 # 每张图片为28*28，所以权重为28*28，\n",
    "num_outputs = 10 # 输出为维度10，\n",
    "# w 为784*10的矩阵\n",
    "w = torch.normal(0.0, 0.01, [num_inputs, num_outputs], requires_grad=True)\n",
    "# 偏置为1*10的行向量\n",
    "b = torch.zeros(num_outputs, requires_grad=True)\n",
    "w.shape,b.shape"
   ],
   "id": "126d9fcbd71111b7",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## 6.定义softmax的操作\n",
    "- 对每个项求幂运算\n",
    "- 对每一行求和，得到每个样本的规范化参数\n",
    "- 将每一行中的项除以规范化参数，确保结果为1"
   ],
   "id": "ead0aeecca414a17"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "def softmax(x):\n",
    "    x_exp = torch.exp(x)\n",
    "    # torch.sum(x, dim=1, keepdim=True)每行求和 ；torch.sum(x, dim=0, keepdim=True) 每列求和\n",
    "    x_exp_sum = torch.sum(x_exp, dim=1, keepdim=True) # 每行求和\n",
    "    return x_exp / x_exp_sum"
   ],
   "id": "a116249e7251aa70",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## 7.定义模型\n",
    "y = wx + b"
   ],
   "id": "c88ec16077906dcb"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "def net(x):\n",
    "    return softmax(torch.matmul(x.reshape(-1,w.shape[0]), w) + b) # 10个维度，转换为列向量"
   ],
   "id": "e76ac82de2630a70",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 8.更新模型参数",
   "id": "3373e07f293dd420"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "def updater(params,lr,batch_size):\n",
    "    \"\"\"\n",
    "    j = j - lr * g(x)\n",
    "    小批量随机梯度下降\n",
    "    :param params: \n",
    "    :param lr: 学习率\n",
    "    :param batch_size: \n",
    "    :return: \n",
    "    \"\"\"\n",
    "    with torch.no_grad(): # 禁用梯度计算 ? \n",
    "        for param in params:\n",
    "            param -= lr / batch_size * param.grad \n",
    "            param.grad.zero_() # 清空梯度\n",
    "            \n",
    "def loss_fn(y_hat, y): # ? 交叉熵\n",
    "    return -torch.log(y_hat[range(len(y_hat)), y])"
   ],
   "id": "e424723cf73e21a",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 9.训练模型",
   "id": "3d3864167c044922"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "lr = 0.1\n",
    "for X,y in train_loader:\n",
    "    y_hat = net(X)\n",
    "    l = loss_fn(y_hat,y)\n",
    "    l.sum().backward() # 反向传播自动微分获取w和b的梯度\n",
    "    updater([w,b],lr,10)\n",
    "w,b    "
   ],
   "id": "a8a3f75bcfdadcb7",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 10.预测",
   "id": "ef8c2217ef3bf85e"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "",
   "id": "9e3e7728b1e5e579"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "for X,y in test_loader:\n",
    "    break;\n",
    "trues = get_labels(y)\n",
    "preds = get_labels(net(X).argmax(axis=1))\n",
    "print(trues)\n",
    "print(preds)"
   ],
   "id": "9347a446c17fb9a6",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-07T01:49:10.211841Z",
     "start_time": "2024-11-07T01:49:10.203838Z"
    }
   },
   "cell_type": "code",
   "source": [
    "a = torch.tensor([1,5,4,3,21,43])\n",
    "a.argmax(axis=0)"
   ],
   "id": "73c97545ef7a50a9",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor(5)"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 20
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": "",
   "id": "826f3d52223b9453"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
